package com.gitee.wsl.data.loader


import com.gitee.wsl.coroutines.atomic.lock.MutatorMutex
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update

interface StateLoader {

    /** 状态 */
    val state: LoaderState

    /** 状态流 */
    val stateFlow: StateFlow<LoaderState>

    /**
     * 是否正在加载中
     */
    fun isLoading(): Boolean

    /**
     * 开始加载，如果上一次加载还未完成，再次调用此方法，会取消上一次加载([CancellationException])，
     * 如果[onLoad]触发了，则[onFinish]一定会触发，[onLoad]的异常会被捕获，除了[CancellationException]
     *
     * @param onFinish 结束回调
     * @param onLoad 加载回调
     */
    suspend fun <T> load(
        onFinish: () -> Unit = {},
        onLoad: suspend () -> T,
    ): Result<T>

    /**
     * 取消加载
     */
    suspend fun cancelLoad()
}

/**
 * 创建[StateLoader]
 */
fun stateLoader(): StateLoader = StateLoaderImpl()

//-------------------- state --------------------

data class LoaderState(
    /** 是否正在加载中 */
    val isLoading: Boolean = false,
)

//-------------------- impl --------------------

private class StateLoaderImpl : StateLoader {

    private val _mutator = MutatorMutex()
    private val _state = MutableStateFlow(LoaderState())

    override val state: LoaderState
        get() = _state.value

    override val stateFlow: StateFlow<LoaderState>
        get() = _state.asStateFlow()

    override fun isLoading(): Boolean {
        return state.isLoading
    }

    override suspend fun <T> load(
        onFinish: () -> Unit,
        onLoad: suspend () -> T,
    ): Result<T> {
        return _mutator.mutate {
            try {
                _state.update { it.copy(isLoading = true) }
                onLoad().let { data ->
                    currentCoroutineContext().ensureActive()
                    Result.success(data)
                }
            } catch (e: Throwable) {
                if (e is CancellationException) throw e
                Result.failure(e)
            } finally {
                _state.update { it.copy(isLoading = false) }
                onFinish()
            }
        }
    }

    override suspend fun cancelLoad() {
        _mutator.cancelAndJoin()
    }
}